using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;

namespace csammisrun.MsnLib.Utility
{
  public class CommandProcessor
  {
    private object _data = null;
    private Session _parent = null;
    private Queue<Transaction> _sendqueue = new Queue<Transaction>();
    private List<Transaction> _history = new List<Transaction>();
    private ServerConnection _conn = null;
    private CommandRegistrar _registry = new CommandRegistrar();
    private bool _usethreadpooldispatch = false;

    private int _transID = 0;

    /// <summary>
    /// Gets a <see cref="CommandRegistrar"/> that dispatches commands for this processor
    /// </summary>
    internal CommandRegistrar Registry
    {
      get { return _registry; }
      set { _registry = value; }
    }

    public CommandProcessor(Session parent)
    {
      _parent = parent;
    }

    public void EnqueueTransaction(Transaction trans)
    {
      lock (_sendqueue)
      {
        _sendqueue.Enqueue(trans);
      }
    }

    /// <summary>
    /// Sends all outstanding transations immediately
    /// </summary>
    public void FlushSendQueue()
    {
      lock (_sendqueue)
      {
        while (_sendqueue.Count > 0)
        {
          SendTransaction(_sendqueue.Dequeue());
        }
      }
    }

    /// <summary>
    /// Sends a command to the server
    /// </summary>
    public void SendCommand(string command, string commandformat, params object[] args)
    {
      SendTransaction(new Transaction(this, command, commandformat, args));
    }

    /// <summary>
    /// Send a <see cref="Transaction"/> to the server
    /// </summary>
    public void SendTransaction(Transaction trans)
    {
      string datastr = "";
      int datastrsize = 0;
      int datasize = 0;
      byte[] data = null;

      trans.ID = this.NextTransactionID;
      _history.Add(trans);

      datastr = trans.ToString();
      datastrsize = Encoding.UTF8.GetByteCount(datastr);
      datasize = datastrsize;
      if (trans.Payload != null)
      {
        datasize += trans.Payload.Length;
      }

      data = new byte[datasize];
      Array.Copy(Encoding.UTF8.GetBytes(datastr), data, datastrsize);
      Logging.WriteString(String.Format("Sending command {0}\r\n", datastr.Trim()));
      // Append transaction's payload to the data
      if (trans.Payload != null)
      {
        Array.Copy(trans.Payload, 0, data, datastrsize, trans.Payload.Length);
      }

      if (!_conn.SendPacket(data))
      {
        // Huh
      }
    }

    /// <summary>
    /// Gets a transaction from the cache
    /// </summary>
    private Transaction FindTransactionInHistory(int id)
    {
      //if (_parent.Userlist.IsSyncing)
      //{
      //  return null;
      //}

      lock (_history)
      {
        Transaction retval = null;
        foreach (Transaction trans in _history)
        {
          if (trans.ID == id)
          {
            retval = trans;
          }
        }
        if (retval != null)
        {
          _history.Remove(retval);
        }
        return retval;
      }
    }

    /// <summary>
    /// Process a command sent by the server
    /// </summary>
    public void DispatchCommand(MsnCommand command)
    {
      Logging.WriteString(String.Format("Dispatching command {0}\r\n", command));
      if(_usethreadpooldispatch)
      {
        ThreadPool.QueueUserWorkItem(new WaitCallback(ProcessCommand), command);
      }
      else
      {
        ProcessCommand(command);
      }
    }

    /// <summary>
    /// Dispatch a message sent by the server
    /// </summary>
    public void ProcessMessage(Message msg)
    {
      List<MessageHandler> handlers = _registry.GetHandlersForMessage(msg.ContentType);
      if (handlers != null)
      {
        foreach (MessageHandler handler in handlers)
        {
          handler(this, msg);
        }
      }
    }

    /// <summary>
    /// Dispatch a command sent by the server
    /// </summary>
    private void ProcessCommand(object obj)
    {
      if (!(obj is MsnCommand))
      {
        return;
      }

      MsnCommand cmd = obj as MsnCommand;

      List<CommandHandler> handlers = null;
      Transaction trans = null;
      bool handled = false;
      int error = 0;

      // Find the transaction that triggered this command, if any
      trans = FindTransactionInHistory(cmd.TransactionID);
      if (trans != null)
      {
        trans.StopTimeout();
        cmd.Reference = trans;
      }

      // Check to see if this command indicates an error in server-side processing
      if (cmd.Command[0] >= '0' && cmd.Command[0] <= '9')
      {
        Int32.TryParse(cmd.Command, out error);
        if (trans != null)
        {
          if (!trans.RaiseError(error))
          {
            // Raise the error on the parent session
          }
        }
        return;
      }

      handlers = _registry.GetHandlersForCommand((trans == null) ? null : trans.Command, cmd.Command);
      foreach (CommandHandler handler in handlers)
      {
        handler(this, cmd);
        handled = true;
      }

      if (!handled)
      {
        // Unhandled command
      }

      if (trans != null && trans.Dependent != null)
      {
        trans.ClearTriggeringCommand();
      }
    }

    #region Properties
    /// <summary>
    /// Gets or sets the server connection using this command processor
    /// </summary>
    public ServerConnection Connection
    {
      get { return _conn; }
      set { _conn = value; }
    }

    /// <summary>
    /// Gets or sets a context-dependent piece of data
    /// </summary>
    public object Data
    {
      get { return _data; }
      set { _data = value; }
    }

    /// <summary>
    /// Gets or sets a value indicating whether the command processor should use the ThreadPool to dispatch received commands
    /// </summary>
    /// <remarks>If the object using this CommandProcessor is a <see cref="Notification"/> object, it's probably not a good idea to set this to <c>true</c> until the parent receives a SYN.</remarks>
    public bool UseThreadPoolDispatch
    {
      get { return _usethreadpooldispatch; }
      set { _usethreadpooldispatch = value; }
    }

    private int NextTransactionID
    {
      get
      {
        lock (this)
        {
          return ++_transID;
        }
      }
    }
    #endregion
  }
}